package io.gary.common.util.execel.processor;

import com.sun.javafx.collections.UnmodifiableListSet;
import io.gary.common.util.execel.SheetCell;
import io.gary.common.util.execel.annotation.SheetColumn;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.ss.usermodel.Cell;

public abstract class ColumnProcessor {

    /**
     * 对应字段
     */
    Field field;
    /**
     * 类型
     */
    Class<?> type;
    /**
     * 标题
     */
    String title;
    /**
     * 索引
     */
    int index = - 1;
    /**
     * 第一列的索引
     */
    int firstColumn = - 1;
    /**
     * 最后一列的索引
     */
    int lastColumn = - 1;
    /**
     * 是否允许空
     */
    boolean nullable;

    abstract public void setData(Object data, Cell dataCell) throws IllegalAccessException, InstantiationException;

    protected Object getCellValue(Cell cell) {
        try {
            if (Objects.isNull(cell)) {
                return null;
            }
            Class clazz = this.type;
            Objects.requireNonNull(clazz);
            Object value = null;
            if (clazz == String.class) {
                value = cell.getStringCellValue();
                value = value == null ? "" : value;
            } else if (clazz == Integer.class) {
                value = (int)cell.getNumericCellValue();
            } else if (clazz == Long.class) {
                value = (long)cell.getNumericCellValue();
            } else if (clazz == Float.class) {
                value = (float)cell.getNumericCellValue();
            } else if (clazz == Double.class) {
                value = cell.getNumericCellValue();
                value = value == null ? 0 : value;
            } else if (clazz == Date.class) {
                value = cell.getDateCellValue();
            }
            if (! this.nullable) {
                Objects.requireNonNull(value);
            }
            return value;
        } catch (Exception e) {
            throw e;
        }
    }
}

class ProcessorFactory {

    private static final Set<Class<?>> BASE_TYPES;

    static {
        Class<?>[] types = {Integer.class, Long.class, String.class, Float.class, Double.class, Date.class};
        BASE_TYPES = new UnmodifiableListSet<>(Arrays.asList(types));
    }

    public static WorkBookProcessor read(Class<?> tClass) {
        Field[] fields = FieldUtils.getAllFields(tClass);
        //生成title信息
        List<ColumnProcessor> columnProcessorList = new LinkedList<>();
        for (Field field : fields) {
            if (! field.isAnnotationPresent(SheetColumn.class)) {
                continue;
            }
            Class<?> fieldType = field.getType();
            SheetColumn sheetColumn = field.getAnnotation(SheetColumn.class);
            ColumnProcessor columnProcessor;
            if (fieldType.isPrimitive() && BASE_TYPES.contains(fieldType)) {
                columnProcessor = new BaseProcessor();
            } else if (fieldType.equals(List.class)) {
                columnProcessor = new ListProcessor((ParameterizedType)field.getGenericType());
            } else if (fieldType.equals(Map.class)) {
                columnProcessor = new MapProcessor((ParameterizedType)field.getGenericType());
            } else {
                columnProcessor = new SimpleBeanProcessor(fieldType);
            }
            columnProcessor.title = sheetColumn.value();
            columnProcessor.nullable = sheetColumn.nullable();
            columnProcessor.type = field.getType();
            columnProcessor.field = field;

            columnProcessorList.add(columnProcessor);
        }
        return new WorkBookProcessor(columnProcessorList);
    }
}

@Getter
@AllArgsConstructor
class WorkBookProcessor {

    private List<ColumnProcessor> columnProcessors;
}

class BaseProcessor extends ColumnProcessor {

    //    public BaseProcessor()

    @Override
    public void setData(Object data, Cell dataCell) throws IllegalAccessException {
        if (Objects.isNull(dataCell)) {
            return;
        }
        Object value = this.getCellValue(dataCell);
        FieldUtils.writeField(this.field, data, value, true);
    }
}

class ListProcessor extends ColumnProcessor {

    private Class<? extends SheetCell> childType;
    private Class<?> childValueType;

    private List<ColumnProcessor> children;

    public ListProcessor(ParameterizedType genericType) {
        Class<?> genericClazz = (Class<?>)genericType.getActualTypeArguments()[0];
        //只有实现了ExecelCell接口的列表才可以
        if (! SheetCell.class.isAssignableFrom(genericClazz)) {
            throw new IllegalArgumentException(
                "invalid type list:" + genericClazz.getName() + ".must be SheetCell interface");
        }
        //设置值的类型
        this.childType = (Class<? extends SheetCell>)genericClazz;
        for (Type inters : genericClazz.getGenericInterfaces()) {
            if (inters instanceof ParameterizedType) {
                ParameterizedType intersParameterizedType = (ParameterizedType)inters;
                if (! intersParameterizedType.getRawType().equals(SheetCell.class)) {
                    continue;
                }
                Type childValueType = intersParameterizedType.getActualTypeArguments()[0];
                this.childValueType = (Class<?>)childValueType;
            }
        }
        if (this.childValueType == null) {
            throw new IllegalArgumentException("SheetCell not set type");
        }
    }


    @Override
    public void setData(Object data, Cell dataCell) throws IllegalAccessException, InstantiationException {

        //初始化列表
        List<? super SheetCell> subValueList = (List<? super SheetCell>)FieldUtils
            .readField(this.field, data, true);
        if (CollectionUtils.isEmpty(subValueList)) {
            subValueList = new ArrayList<>(this.lastColumn - this.firstColumn);
        }
        int index = dataCell.getAddress().getColumn();
        int pos = index - this.firstColumn;
        ColumnProcessor columnProcessor = this.childs.get(pos);
        Object subValue = columnProcessor.getCellValue(dataCell);
        SheetCell subData = this.childType.newInstance();
        subData.setTitle(columnProcessor.title);
        subData.setValue(subValue);
        subValueList.add(pos, subData);
        FieldUtils.writeField(this.field, data, subValueList, true);
    }
}

class MapProcessor extends ColumnProcessor {

    private List<ColumnProcessor> children;

    public MapProcessor(ParameterizedType genericType) {}

    @Override
    public void setData(Object data, Cell dataCell) throws IllegalAccessException, InstantiationException {

    }
}

class SimpleBeanProcessor extends ColumnProcessor {

    private List<ColumnProcessor> children;

    public SimpleBeanProcessor(Class<?> tClass) {
        WorkBookProcessor workBookProcessor = ProcessorFactory.read(tClass);
        children = workBookProcessor.getColumnProcessors();
    }

    @Override
    public void setData(Object data, Cell dataCell) throws IllegalAccessException, InstantiationException {
        if (CollectionUtils.isEmpty(this.children)) {
            return;
        }
        ColumnProcessor childProcessor = this.findChild(index);
        if (Objects.isNull(childProcessor)) {
            return;
        }

        Object subData = FieldUtils.readField(this.field, data, true);
        if (Objects.isNull(subData)) {
            subData = this.type.newInstance();
        }
        childProcessor.setData(subData, dataCell);
        //        Object subValue = childProcessor.getCellValue(dataCell);
        //        FieldUtils.writeField(childProcessor.field, subData, subValue, true);
        FieldUtils.writeField(this.field, data, subData, true);
    }

    private ColumnProcessor findChild(int index) {
        for (ColumnProcessor columnProcessor : this.children) {
            if (columnProcessor.index == index) {
                return columnProcessor;
            }
        }
        return null;
    }
}

